package se.devoteam.nexus.ldap;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.concurrent.locks.ReentrantLock;

import javax.naming.directory.SearchControls;

import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.logging.AbstractLogEnabled;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;

import se.devoteam.nexus.ldap.model.Configuration;
import se.devoteam.nexus.ldap.model.UserMappingConfiguration;
import se.devoteam.nexus.ldap.model.io.xpp3.LdapRealmConfigurationXpp3Reader;

/**
 * @author Anders Hammar
 */
@Component(role = LdapConfigurationManager.class)
public class DefaultLdapConfigurationManager extends AbstractLogEnabled implements LdapConfigurationManager {

    @org.codehaus.plexus.component.annotations.Configuration(value = "${ldap-xml-file}")
	private File ldapConfiguration;

	/**
	 * This will hold the current configuration in memory, to reload set this to null.
	 */
	private Configuration configuration = null;

	private ReentrantLock lock = new ReentrantLock();

	public String getProviderUrl() {
		return getConfiguration().getProviderUrl();
	}

	public String getSecurityCredentials() {
		return getConfiguration().getSecurityCredentials();
	}

	public String getSecurityPrincipal() {
		return getConfiguration().getSecurityPrincipal();
	}

	public String getSecurityProtocol() {
		return getConfiguration().getSecurityProtocol();
	}

	public String getSecurityAuthentication() {
		return getConfiguration().getSecurityAuthentication();
	}

	public String getInitialContextFactory() {
		String ctxFactoryClassName = getConfiguration().getInitialContextFactory();
		if (ctxFactoryClassName == null) {
			ctxFactoryClassName = Configuration.DEFAULT_CONTEXT_FACTORY_CLASS;
		}
		return ctxFactoryClassName;
	}

    public String getUserIdAttribute() {
    	String userIdAttribute = getConfiguration().getUserMapping().getUserIdAttribute();
    	if (userIdAttribute == null) {
    		userIdAttribute = UserMappingConfiguration.DEFAULT_USERID_ATTRIBUTE;
    	}
    	return userIdAttribute;
    }

    public String getNameAttribute() {
    	String nameAttribute = getConfiguration().getUserMapping().getNameAttribute();
    	if (nameAttribute == null) {
    		nameAttribute = UserMappingConfiguration.DEFAULT_NAME_ATTRIBUTE;
    	}
    	return nameAttribute;
    }

    public String getEmailAddressAttribute() {
    	String emailAttribute = getConfiguration().getUserMapping().getEmailAddressAttribute();
    	if (emailAttribute == null) {
    		emailAttribute = UserMappingConfiguration.DEFAULT_EMAILADDRESS_ATTRIBUTE;
    	}
    	return emailAttribute;
    }
    
    public String getUserBaseDn() {
    	return getConfiguration().getUserMapping().getBaseDn();
    }
    
    public int getUserSearchScope() {
    	boolean searchSubtree = getConfiguration().getUserMapping().isSearchSubtree();
    	int result;
		if (searchSubtree) {
			result = SearchControls.SUBTREE_SCOPE;
		} else {
			result = SearchControls.ONELEVEL_SCOPE;
		}
    	
    	return result;
    }
    
    public String getUserObjectClass() {
    	String userObjectClass = getConfiguration().getUserMapping().getObjectClass();
    	if (userObjectClass == null) {
    		userObjectClass = UserMappingConfiguration.DEFAULT_OBJECT_CLASS;
    	}
    	return userObjectClass;
    }

    public String getSearchUserFilterExpr() {
		StringBuilder str = new StringBuilder();
		str.append("(&(objectClass=");
		str.append(getUserObjectClass());
		str.append(")(");
		str.append(getUserIdAttribute());
		str.append("={0}))");
		
		return str.toString();
	}

	public String getSearchUsersFilterExpr() {
		StringBuilder str = new StringBuilder();
		str.append("(&(objectClass=");
		str.append(getUserObjectClass());
		str.append(")(");
		str.append(getUserIdAttribute());
		str.append("={0}*))");
		
		return str.toString();
	}

	public String getListUsersFilterExpr() {
		String userObjClass = getUserObjectClass();
		StringBuilder str = new StringBuilder();
		str.append("(objectclass=");
		str.append(userObjClass);
		str.append(')');
		
		return str.toString();
	}

	public void clearCache() {
		// Just to make sure we aren't fiddling w/ save/load process
		lock.lock();
		this.configuration = null;
		lock.unlock();
	}

	private Configuration getConfiguration() {
		if (this.configuration != null) {
			return this.configuration;
		}

		this.lock.lock();

		Reader fr = null;
		try {
			getLogger().debug("Reading configuration file");
			FileInputStream is = new FileInputStream(ldapConfiguration);
			LdapRealmConfigurationXpp3Reader reader = new LdapRealmConfigurationXpp3Reader();
			fr = new InputStreamReader(is);

			this.configuration = reader.read(fr);
		} catch (FileNotFoundException e) {
			getLogger().error("Configuration file not found; please verify configuration of the 'ldap-xml-file' property", e);
			throw new RuntimeException("LDAP realm configuration file not found");
		} catch (IOException e) {
			getLogger().error("IOException while retrieving configuration file", e);
			throw new RuntimeException("Cannot read LDAP realm configuration file");
		} catch (XmlPullParserException e) {
			getLogger().error("Invalid XML configuration", e);
			throw new RuntimeException("Cannot read LDAP realm configuration file");
		} finally {
			if (fr != null) {
				try {
					fr.close();
				} catch (IOException e) {
					// just closing if open
				}
			}

			lock.unlock();
		}

		return this.configuration;
	}
}
